﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Laos;
using PostSharp.Extensibility;
using System.Reflection;

namespace EditableBusinessObjects.Postsharp.Public
{
    /// <summary>
    /// Applied to a class will rewrite the way fields are accessed
    /// to make backups. Will also 'inject' methods in the class to be able to
    /// set a edit mode (beginedit, canceledit, endedit) and copy current values
    /// to the loadedvalues collection
    /// </summary>
    /// <remarks>Cast your object to the <see cref="IEditableBusinessObject"/> IEditableBusinessObject
    /// interface to be able to interact with the new functionality</remarks>
    [Serializable]
    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    [MulticastAttributeUsage(MulticastTargets.Class, AllowMultiple = false)]
    public class EditableBusinessObjectAttribute : CompoundAspect
    {
        /// <summary>
        /// Gets or sets the priority of the property-level aspect.
        /// </summary>
        /// <remarks>
        /// Give a large number to have the event raisen after any other
        /// on-success aspect on the properties of this type. The default value
        /// is 0.
        /// </remarks>
        public int AspectPriority { get { return aspectPriority; } set { aspectPriority = value; } }
        [NonSerialized]
        private int aspectPriority = 0;


        /// <summary>
        /// Provides new aspects. Injects the IEditableBusinessObject interface on the class
        /// and look at all the fields to intercept it's accessors.
        /// </summary>
        /// <param name="element">Element to which the current custom attribute is applied.</param>
        /// <param name="collection">Collection into which
        /// new aspects have to be added.</param>
        public override void ProvideAspects(object element, LaosReflectionAspectCollection collection)
        {
            // Get the target type.
            Type typeDef = (Type)element;

            // implement the INotifyPropertyChanged interface on the class
            collection.AddAspect(typeDef, new AddNotifyPropertyChangedInterfaceSubAspect());

            // implement our editable interface
            collection.AddAspect(typeDef, new EditableBusinessObjectAspect());

            foreach (FieldInfo field in typeDef.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
            {
                collection.AddAspect(field, new FieldInterceptionAspect());
            }

            #region add notifyproperty changed to individual properties
            foreach (PropertyInfo property in typeDef.UnderlyingSystemType.GetProperties())
            {
                if (property.DeclaringType == typeDef && property.CanWrite)
                {
                    MethodInfo method = property.GetSetMethod();

                    if (!method.IsStatic)
                    {
                        // throw notifypropertychanged events (on all properties)
                        collection.AddAspect(method, new OnPropertySetNotifyPropertyChangedSubAspect(property.Name, this.AspectPriority));
                    }
                }
            }
            #endregion
            
        }
    }
}
